package com.example.servicegateway.authentication;

import com.example.servicegateway.authentication.constant.ResultCode;
import com.example.servicegateway.authentication.util.WebFluxUtils;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 资源服务器配置
 * 注解需要使用@EnableWebFluxSecurity而非@EnableWebSecurity,因为SpringCloud Gateway基于WebFlux
 * 这里的工作是将鉴权管理器ResourceServerManager配置到资源服务器、请求白名单放行、无权访问和无效token的自定义异常响应
 */
@Slf4j
@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "security")
public class ResourceServerConfig {

    private final ResourceServerManager resourceServerManager;

    @Setter
    private List<String> ignoreUrls;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {

        http.oauth2ResourceServer()
                .jwt()
                // 定义权限管理器，转换器JwtGrantedAuthoritiesConverter
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
                // 本地加载公钥(配置文件使用远程加载service-auth的publicKey，不用加载本地publicKey)
//                .publicKey(rsaPublicKey());

        // 资源服务器处理未认证
        http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());
        // 鉴权配置
        http.authorizeExchange()
                // 白名单放行
                .pathMatchers(ignoreUrls.toArray(new String[]{})).permitAll()
                // 接口鉴权处理
                .anyExchange().access(resourceServerManager)
                .and()
                .exceptionHandling()
                // 处理未授权
                .accessDeniedHandler(accessDeniedHandler())
                // 处理未认证
                .authenticationEntryPoint(authenticationEntryPoint())
                .and().csrf().disable();

        return http.build();
    }

    /**
     * <a href="https://blog.csdn.net/qq_24230139/article/details/105091273">https://blog.csdn.net/qq_24230139/article/details/105091273</a>
     * ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication
     * 需要把jwt的Claim中的authorities加入
     * 方案：重新定义权限管理器，默认转换器JwtGrantedAuthoritiesConverter
     */
    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }

//    /**
//     * 本地获取JWT验签公钥
//     */
//    @Bean
//    public RSAPublicKey rsaPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
//        Resource resource = new ClassPathResource("public.key");
//        InputStream is = resource.getInputStream();
//        String publicKeyData = IoUtil.read(is).toString();
//        X509EncodedKeySpec keySpec = new X509EncodedKeySpec((Base64.decode(publicKeyData)));
//
//        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
//        return rsaPublicKey;
//    }

    /**
     * token无效或者已过期自定义响应
     */
    public ServerAuthenticationEntryPoint authenticationEntryPoint() {
        return (exchange, e) -> Mono.defer(() -> Mono.just(exchange.getResponse()))
                    .flatMap(response -> WebFluxUtils.writeResponse(response, ResultCode.TOKEN_INVALID_OR_EXPIRED));
    }

    /**
     * 自定义未授权响应
     */
    public ServerAccessDeniedHandler accessDeniedHandler() {
        return (exchange, denied) -> Mono.defer(() -> Mono.just(exchange.getResponse()))
                    .flatMap(response -> WebFluxUtils.writeResponse(response, ResultCode.ACCESS_UNAUTHORIZED));
    }
}
